﻿
#include "FrameResource.h"
#include "../../Common/D3DApp.h"
#include "FrameResource.h"
#include "../../Common/D3DUtil.h"
#include "Pass/Pass.h"
#include "../../Common/Singleton.h"
#include "../../Common/Camera.h"
#include "Pass/BasePass.h"
#include "ResourceManager/DX3GeometryManager.h"
#include "ResourceManager/DX3MaterailManager.h"
#include "ResourceManager/DX3RootSingtureManager.h"
#include "ResourceManager/DX3ShadersAndInputLayoutManager.h"
#include "ResourceManager/DX3TextureManager.h"


class Pass;
using Microsoft::WRL::ComPtr;
using namespace DirectX;
using namespace DirectX::PackedVector;

const int gNumFrameResources = 3;

class CameraAndDynamicIndexingApp : public D3DApp
{
public:
    CameraAndDynamicIndexingApp(HINSTANCE hInstance);
    CameraAndDynamicIndexingApp(const CameraAndDynamicIndexingApp& rhs) = delete;
    CameraAndDynamicIndexingApp& operator=(const CameraAndDynamicIndexingApp& rhs) = delete;
    ~CameraAndDynamicIndexingApp();
    
    virtual bool Initialize() override;
    
private:
    virtual void OnResize() override;
    virtual void Update(const GameTimer& gt) override;
    virtual void Draw(const GameTimer& gt) override;

    virtual void OnMouseDown(WPARAM btnState, int x, int y)override;
    virtual void OnMouseUp(WPARAM btnState, int x, int y)override;
    virtual void OnMouseMove(WPARAM btnState, int x, int y)override;

	void OnKeyboardInput(const GameTimer& gt);
	void AnimateMaterials(const GameTimer& gt);
	void UpdateObjectCBs(const GameTimer& gt);
	void UpdateMaterialBuffer(const GameTimer& gt);
	void UpdateMainPassCB(const GameTimer& gt);

	void LoadTextures();
    void BuildRootSignature();
    void BuildDescriptorHeaps();
    void BuildShadersAndInputLayout();
    void BuildShapeGeometry();
 
    void BuildFrameResources();
    void BuildMaterials();
	
    void CreatePasses();
    void InitialPasses();

private:

private:
    std::vector<std::unique_ptr<FrameResource>> mFrameResources;
    FrameResource* mCurrFrameResource = nullptr;
    int mCurrFrameResourceIndex = 0;
    UINT mCbvSrvDescriptorSize = 0;

    //srv description

    std::unordered_map<std::string,std::shared_ptr<Pass>> mAllPasses;
    std::unordered_map<std::string,std::shared_ptr<Pass>> mMainPasses;

    // List of all the render items.
    std::vector<std::unique_ptr<RenderItem>> mAllRitems;
	// Render items divided by PSO.
	std::vector<RenderItem*> mOpaqueRitems;

    PassConstants mMainPassCB;

    Camera mCamera;

    POINT mLastMousePos;

    //remove 
 	Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mSrvDescriptorHeap = nullptr;
    std::unordered_map<std::string, ComPtr<ID3D12PipelineState>> mPSOs;
    
    std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
                   PSTR cmdLine, int showCmd)
{
	
    // Enable run-time memory check for debug builds.
    #if defined(DEBUG) | defined(_DEBUG)
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif	

    try
    {
        CameraAndDynamicIndexingApp theApp(hInstance);

        if (!theApp.Initialize())
            return 0;

        return theApp.Run();
    }
    catch (DxException& e)
    {
        MessageBox(nullptr, e.ToString().c_str(), L"HR Failed", MB_OK);
        return 0;
    }
}

CameraAndDynamicIndexingApp::CameraAndDynamicIndexingApp(HINSTANCE hInstance)
    : D3DApp(hInstance)
{
}

CameraAndDynamicIndexingApp::~CameraAndDynamicIndexingApp()
{
    if(md3dDevice != nullptr)
        FlushCommandQueue();
}

bool CameraAndDynamicIndexingApp::Initialize()
{
    if(!D3DApp::Initialize())
        return false;

    // Reset the command list to prep for initialization commands.
    ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));

    // Get the increment size of a descriptor in this heap type.  This is hardware specific, 
    // so we have to query this information.
    mCbvSrvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

    mCamera.SetPosition(0.0f, 2.0f, -15.0f);
 
    LoadTextures();
    BuildRootSignature();
    BuildDescriptorHeaps();
    BuildShadersAndInputLayout();
    BuildShapeGeometry();
    BuildMaterials();

	// BuildRenderItems();
	// BuildPSOs();

	CreatePasses();
	InitialPasses();
	
	BuildFrameResources();


    // Execute the initialization commands.
    ThrowIfFailed(mCommandList->Close());
    ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
    mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

    // Wait until initialization is complete.
    FlushCommandQueue();

    return true;
}

void CameraAndDynamicIndexingApp::OnResize()
{
    D3DApp::OnResize();

    mCamera.SetLens(0.25f * MathHelper::Pi, AspectRatio(), 1.0f, 1000.0f);
}

void CameraAndDynamicIndexingApp::Update(const GameTimer& gt)
{
	OnKeyboardInput(gt);

	// Cycle through the circular frame resource array.
	mCurrFrameResourceIndex = (mCurrFrameResourceIndex + 1) % gNumFrameResources;
	mCurrFrameResource = mFrameResources[mCurrFrameResourceIndex].get();

	// Has the GPU finished processing the commands of the current frame resource?
	// If not, wait until the GPU has completed commands up to this fence point.
	if(mCurrFrameResource->Fence != 0 && mFence->GetCompletedValue() < mCurrFrameResource->Fence)
	{
		HANDLE eventHandle = CreateEventEx(nullptr,FALSE,FALSE,EVENT_ALL_ACCESS);
		ThrowIfFailed(mFence->SetEventOnCompletion(mCurrFrameResource->Fence, eventHandle));
		WaitForSingleObject(eventHandle, INFINITE);
		CloseHandle(eventHandle);
	}

	AnimateMaterials(gt);
	UpdateObjectCBs(gt);
	UpdateMaterialBuffer(gt);
	UpdateMainPassCB(gt);
}

void CameraAndDynamicIndexingApp::Draw(const GameTimer& gt)
{
    auto cmdListAlloc = mCurrFrameResource->CmdListAlloc;
	   
    ThrowIfFailed(cmdListAlloc->Reset());
    
    ThrowIfFailed(mCommandList->Reset(cmdListAlloc.Get(),nullptr));
    
    ID3D12DescriptorHeap* descriptorHeaps[] = {mSrvDescriptorHeap.Get()};
    mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps),descriptorHeaps);
	   
    mCommandList->RSSetViewports(1,&mScreenViewport);
    mCommandList->RSSetScissorRects(1,&mScissorRect);
    
    ID3D12Resource* RenderTarget;
    D3D12_CPU_DESCRIPTOR_HANDLE RenderTargetView;
    D3D12_CPU_DESCRIPTOR_HANDLE RenderDepthStencilView;
    
    {
        RenderTarget = CurrentBackBuffer();
        RenderTargetView = CurrentBackBufferView();
        RenderDepthStencilView = DepthStencilView();
    
        //transition backbuffer
        auto transitionBackBuffer = CD3DX12_RESOURCE_BARRIER::Transition(RenderTarget,
            D3D12_RESOURCE_STATE_PRESENT,D3D12_RESOURCE_STATE_RENDER_TARGET);
        mCommandList->ResourceBarrier(1,&transitionBackBuffer);
    }
    
    CD3DX12_GPU_DESCRIPTOR_HANDLE gpuHanlder(mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
    UINT objCBByteSize =  d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
    UINT matCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(MaterialData));
    UINT passCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(PassConstants));
    auto objectCB = mCurrFrameResource->ObjectCB->Resource();
    auto matCB = mCurrFrameResource->MaterialBuffer->Resource();
    auto passCB  = mCurrFrameResource->PassCB->Resource();
    
    for(auto it = mMainPasses.begin();it != mMainPasses.end();it++)
    {
        auto curPass = it->second;
        curPass->Draw(
            mCommandList.Get(),
            gpuHanlder,
            mCbvSrvDescriptorSize,
            objCBByteSize,matCBByteSize,passCBByteSize,
            objectCB,matCB,passCB,mMainPassCB,
            RenderTarget,
            RenderTargetView,
            RenderDepthStencilView
            );
    }
    
    //general transition
    auto transitionClose = CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),D3D12_RESOURCE_STATE_RENDER_TARGET,D3D12_RESOURCE_STATE_PRESENT);
    mCommandList->ResourceBarrier(1,
        &transitionClose);
		  
    ThrowIfFailed(mCommandList->Close());
    
    ID3D12CommandList* cmdsLists[] = {mCommandList.Get()};
    mCommandQueue->ExecuteCommandLists(_countof(cmdsLists),cmdsLists);
    
    ThrowIfFailed(mSwapChain->Present(0,0));
    mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;
	   
    mCurrFrameResource->Fence = ++mCurrentFence;
    mCommandQueue->Signal(mFence.Get(), mCurrentFence);
	
}

void CameraAndDynamicIndexingApp::OnMouseDown(WPARAM btnState, int x, int y)
{
	mLastMousePos.x = x;
	mLastMousePos.y = y;

	SetCapture(mhMainWnd);
}

void CameraAndDynamicIndexingApp::OnMouseUp(WPARAM btnState, int x, int y)
{
	ReleaseCapture();
}

void CameraAndDynamicIndexingApp::OnMouseMove(WPARAM btnState, int x, int y)
{
	if((btnState & MK_LBUTTON) != 0)
	{
		// Make each pixel correspond to a quarter of a degree.
		float dx = XMConvertToRadians(0.25f*static_cast<float>(x - mLastMousePos.x));
		float dy = XMConvertToRadians(0.25f*static_cast<float>(y - mLastMousePos.y));

		mCamera.Pitch(dy);
		mCamera.RotateY(dx);
	}

	mLastMousePos.x = x;
	mLastMousePos.y = y;
}

void CameraAndDynamicIndexingApp::OnKeyboardInput(const GameTimer& gt)
{
	const float dt = gt.DeltaTime();

	if(GetAsyncKeyState('W') & 0x8000)
		mCamera.Walk(10.0f*dt);

	if(GetAsyncKeyState('S') & 0x8000)
		mCamera.Walk(-10.0f*dt);

	if(GetAsyncKeyState('A') & 0x8000)
		mCamera.Strafe(-10.0f*dt);

	if(GetAsyncKeyState('D') & 0x8000)
		mCamera.Strafe(10.0f*dt);

	mCamera.UpdateViewMatrix();
}

void CameraAndDynamicIndexingApp::AnimateMaterials(const GameTimer& gt)
{
}

void CameraAndDynamicIndexingApp::UpdateObjectCBs(const GameTimer& gt)
{
	auto curObjCb = mCurrFrameResource->ObjectCB.get();
	for(auto& item : mAllRitems)
	{
		if(item->NumFramesDirty > 0)
		{
			XMMATRIX world = XMLoadFloat4x4(&item->World);
			XMMATRIX texTransform = XMLoadFloat4x4(&item->TexTransform);

			ObjectConstants object_constants;
			XMStoreFloat4x4(&object_constants.World, XMMatrixTranspose(world));
			XMStoreFloat4x4(&object_constants.TexTransform, XMMatrixTranspose(texTransform));
			object_constants.MaterialIndex = item->Mat->MatCBIndex;

			curObjCb->CopyData(item->ObjCBIndex,object_constants);
			item->NumFramesDirty -- ;
		}
	}
}

void CameraAndDynamicIndexingApp::UpdateMaterialBuffer(const GameTimer& gt)
{
	auto currMaterialBuffer = mCurrFrameResource->MaterialBuffer.get();
	
	for(auto& e : Singleton<DX3MaterailManager>::Instance()->GetMaterials())
	{
		Material* mat = e.second.get();
		if(mat and mat->NumFramesDirty > 0)
		{
			XMMATRIX matTransform = XMLoadFloat4x4(&mat->MatTransform);

			MaterialData matData;
			matData.DiffuseAlbedo = mat->DiffuseAlbedo;
			matData.FresnelR0 = mat->FresnelR0;
			matData.Roughness = mat->Roughness;
			XMStoreFloat4x4(&matData.MatTransform, XMMatrixTranspose(matTransform));
			matData.DiffuseMapIndex = mat->DiffuseSrvHeapIndex;

			currMaterialBuffer->CopyData(mat->MatCBIndex, matData);
			mat->NumFramesDirty --;
		}
	}
}

void CameraAndDynamicIndexingApp::UpdateMainPassCB(const GameTimer& gt)
{
	XMMATRIX view = mCamera.GetView();
	XMMATRIX proj = mCamera.GetProj();

	XMMATRIX viewProj = XMMatrixMultiply(view, proj);

	auto viewDeter = XMMatrixDeterminant(view);
	auto projDeter = XMMatrixDeterminant(proj);
	auto viewProjDeter = XMMatrixDeterminant(viewProj);

    
	XMMATRIX invView = XMMatrixInverse(&viewDeter, view);
	XMMATRIX invProj = XMMatrixInverse(&projDeter, proj);
	XMMATRIX invViewProj = XMMatrixInverse(&viewProjDeter, viewProj);

	XMStoreFloat4x4(&mMainPassCB.View, XMMatrixTranspose(view));
	XMStoreFloat4x4(&mMainPassCB.InvView, XMMatrixTranspose(invView));
	XMStoreFloat4x4(&mMainPassCB.Proj, XMMatrixTranspose(proj));
	XMStoreFloat4x4(&mMainPassCB.InvProj, XMMatrixTranspose(invProj));
	XMStoreFloat4x4(&mMainPassCB.ViewProj, XMMatrixTranspose(viewProj));
	XMStoreFloat4x4(&mMainPassCB.InvViewProj, XMMatrixTranspose(invViewProj));

	mMainPassCB.EyePosW = mCamera.GetPosition3f();
	mMainPassCB.RenderTargetSize = XMFLOAT2((float)mClientWidth,(float)mClientHeight);
	mMainPassCB.InvRenderTargetSize = XMFLOAT2((float)1.0f/mClientWidth,(float)1.0f/mClientHeight);

	mMainPassCB.NearZ = 1.0f;
	mMainPassCB.FarZ = 1000.0f;
	mMainPassCB.TotalTime = gt.TotalTime();
	mMainPassCB.DeltaTime = gt.DeltaTime();

	mMainPassCB.AmbientLight = {0.25f, 0.25f, 0.35f, 1.0f};
	mMainPassCB.Lights[0].Direction = { 0.57735f, -0.57735f, 0.57735f };
	mMainPassCB.Lights[0].Strength = { 0.8f, 0.8f, 0.8f };
	mMainPassCB.Lights[1].Direction ={ -0.57735f, -0.57735f, 0.57735f };
	mMainPassCB.Lights[1].Strength = { 0.4f, 0.4f, 0.4f };
	mMainPassCB.Lights[2].Direction ={ 0.0f, -0.707f, -0.707f };
	mMainPassCB.Lights[2].Strength = { 0.2f, 0.2f, 0.2f };

	auto currPassCB = mCurrFrameResource->PassCB.get();
	currPassCB->CopyData(0, mMainPassCB);
}


void CameraAndDynamicIndexingApp::LoadTextures()
{
    Singleton<DX3TextureManager>::Instance()->LoadTexture(md3dDevice.Get(),mCommandList.Get());
}

void CameraAndDynamicIndexingApp::BuildRootSignature()
{
	Singleton<DX3RootSingtureManager>::Instance()->BuildRootSignature(md3dDevice.Get());
    
}

void CameraAndDynamicIndexingApp::BuildDescriptorHeaps()
{	
    D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
    srvHeapDesc.NumDescriptors = 4;
    srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
    srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
    ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(&mSrvDescriptorHeap)));

    //
    // Fill out the heap with actual descriptors.
    //
    CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
	
	Singleton<DX3TextureManager>::Instance()->BuildDescriptorHeap(md3dDevice.Get(),hDescriptor,mCbvSrvDescriptorSize);
	
}

void CameraAndDynamicIndexingApp::BuildShadersAndInputLayout()
{
	Singleton<DX3ShadersAndInputLayoutManager>::Instance()->BuildShaderAndLayout();
}

void CameraAndDynamicIndexingApp::BuildShapeGeometry()
{
    Singleton<DX3GeometryManager>::Instance()->BuildGeometry(md3dDevice.Get(),mCommandList.Get());
}

void CameraAndDynamicIndexingApp::BuildFrameResources()
{
    for(int i = 0; i < gNumFrameResources; ++i)
    {
        mFrameResources.push_back(std::make_unique<FrameResource>(md3dDevice.Get(),
            1, (UINT)mAllRitems.size(), (UINT)Singleton<DX3MaterailManager>::Instance()->GetMaterials().size()));
    }
}

void CameraAndDynamicIndexingApp::BuildMaterials()
{
    Singleton<DX3MaterailManager>::Instance()->BuildMaterial();
}

void CameraAndDynamicIndexingApp::CreatePasses()
{
    auto basePass = std::make_shared<BasePass>();

    mAllPasses["basePass"] = basePass;
    mMainPasses["basPass"] = basePass;
}

void CameraAndDynamicIndexingApp::InitialPasses()
{
    UINT ObjIndex = 0;
    for(auto it = mAllPasses.begin(); it != mAllPasses.end(); it++)
    {
        auto curPass = it->second;
        curPass->BuildRenderItems(mAllRitems,ObjIndex);
        curPass->BuildPSOs(md3dDevice.Get(),mBackBufferFormat,mDepthStencilFormat,m4xMsaaState,m4xMsaaQuality);
    }
}





